/*LICENSE_BEGIN
 * ******************************************************************
 * Copyright 2006 by the Dainamite Robocup-Team at the
 * DAI-Labor, TU-Berlin (www.dai-labor.de)
 * 
 * Members of Dainamite are:
 * Holger Endert
 * Robert Wetzker
 * Thomas Karbe
 * Axel Heßler
 * Philippe Bütter
 * Felix Brossmann
 * 
 * Licensed under the GNU GPL (General Public License) Free Software 
 * License, Version 2. You may use this file only in compliance with 
 * the License. You may obtain a copy of the License at:
 * 
 * http://www.fsf.org/licensing/licenses/gpl.txt
 * ******************************************************************
LICENSE_END*/
package robocup.component;

import java.util.Properties;

import robocup.component.infotypes.Info;
import robocup.connection.RCSSConnection;
import robocup.parser9.RobocupParser;


/**
 * This class is an abstractation for all agent types like player-agent 
 * (RobocupAgent), coach (CoachAgent) and trainer (TrainerAgent) and thus
 * is extended by all these classes. It incorporates common attributes like
 * <code>HOST_NAME</code> for the network connection as well as methods -
 * abstract and implemented - for control flow of the agents.
 * Since each agent is an independent entity, this class extends <code>Thread
 * </code>. 
 */
public abstract class AbstractAgent extends Thread {


	/**
	 * Constants are used to identify items of the property-file, that should
	 * be given on startup via command-line parameter.
	 */
	public static final String HOST_NAME = "hostname";

	public static final String HOST_PORT = "port";

	public static final String MODE = "mode";

	public static final String TEAM_NAME = "teamname";

	/**
	 * these constants are used to identify the type of agent, which is loaded.
	 * The current type is stored in field <code>mode</code>
	 */
	public static final int FIELD_PLAYER = 0;

	public static final int GOALIE = 1;

	public static final int COACH = 2;

	public static final int TRAINER = 4;

	// The properties that are given in a file on startup via command line
	protected Properties config = null;

	protected int serverPort = 6000;

	protected String serverHost = "localhost";

	protected int mode;

	protected String teamName;

	protected boolean isTeamEast;

	private boolean running = true;

	/**
	 * All main components of the agent: connection: the client, handles send
	 * and receive of messages parser: depending on the mode, a normal or
	 * coach-parser
	 */
	protected RCSSConnection connection = null;

	protected RobocupParser parser = null;

	/**
	 * This class provides the basic features of an agent which connects to the
	 * soccer-server. Implementing all abstract methods, subclasses are able to
	 * define all possible types of agents (player/goalie/coach/trainer).
	 * 
	 * @param props -
	 *            the agents configuration
	 */
	public AbstractAgent(Properties props) {
		super();
		this.config = props;
	}

	/**
	 * This method initializes all basic configuration attributes, i.e. the host
	 * and the port where the server is listening on connections.
	 */
	protected void initBaseConfig() {
		String tmp = null;
		tmp = (String) this.config.get(TEAM_NAME);
		if (tmp != null)
			this.teamName = tmp;

		tmp = (String) this.config.get(HOST_NAME);
		if (tmp != null)
			this.serverHost = tmp;

		tmp = (String) this.config.get(HOST_PORT);
		if (tmp != null)
			this.serverPort = Integer.parseInt(tmp);
		
		tmp = (String) this.config.get(MODE);
		if (tmp != null)
			this.mode = Integer.parseInt(tmp);

		// call to the subclass initialization
		initConfig();
	}

	/**
	 * This method initializes the RCSSConnection by using the host and port
	 * defined in initBaseConnection().
	 */
	private boolean initConnection() {
		System.out.println("[RobocupAgent] Try to connect agent to Server: "
				+ this.serverHost + ":" + this.serverPort);
		if (this.connection == null) {
			this.connection = new RCSSConnection(this.serverHost,
					this.serverPort);
		}
		// calling the connect of the subclass
		return initProtocol();
	}

	/**
	 * This method initializes all neccessary data of the player. This should be
	 * done always before starting the thread!
	 */
	public void initialize() {
		// initialize all
		this.initBaseConfig();
		this.initComponents();
		boolean b = false;
        int count =0;
		while (!b) {
            if (count++>15) {
                System.out.println("Nepodarilo sa spojit so serverom.");
                System.exit(1);
            }
			b = this.initConnection();
		}
	}

	/**
	 * This method overrides the inherited method from <code>Thread</code> by adding a basic
	 * mechanism. This includes receiving messages from the server and parsing
	 * them into Info-Objects. These objects are forwarded to the abstract
	 * method <code>processInfo(Info)</code>, which is implemented in the
	 * extending classes.
	 */
	@Override
	public void run() {

		this.setName("Agent-Thread " + System.currentTimeMillis());
		this.running = true;
		while (this.running){
			try {
				// read data from connection
				String str = this.connection.receive();
				// if a timeout occurs
				if (str == null) {
					System.out.println("Agent terminated!");
					return;
				}

				// parse it, if string is available
				Info info = null;
				if (str != null && str.length() > 1)
					info = this.parser.parseCommand(str);

				if (info != null)
					processInfo(info);
			} catch (Exception e) {
				System.out.println("Error in Agent process:");
				e.printStackTrace();
			}
		}
	}

	/**
	 * These method should be overriden by all subclasses to implement the logic
	 * of information handling, which is very different for different types of
	 * agents (e.g. for players, who have to be synchronized).
	 * 
	 * @param info -
	 *            the received info-object
	 */
	protected abstract void processInfo(Info info);

	/**
	 * This method initializes all necessary data, reading it from the
	 * properties given for that agent.
	 */
	protected abstract void initConfig();

	/**
	 * This method handles the initialization protocol, depending on the
	 * subclass-agent. I.e., a RobocupAgent (Player/Goalie) sends an
	 * init-message and retrieves serverconfigs, playerconfigs, etc.
	 * 
	 * @return - true, if protocol was successfully executed!
	 */
	protected abstract boolean initProtocol();

	/**
	 * This method should be overwritten to initialize all necessary components
	 * of the agent, i.e. the parser, the worldmodel, the tactic, etc.
	 */
	protected abstract void initComponents();

	/**
	 * This method sends a string to the soccerserver, and should only be
	 * invoked by the synchro-component. The string is forwarded to the
	 * RCSSConnection, which sends the string immediately as UDP packet!
	 * 
	 * @param action -
	 *            the action string
	 */
	public void sendActions(String action) {
		try {
			this.connection.send(action);
		} catch (Exception e) {
			System.out.println("ERROR in : AbstractAgent.sendActions");
			e.printStackTrace();
		}
	}

	// ~ --------------------------------------------
	// ~ Methods for accessing the agents type and location

	/**
	 * Retrieves the teamName
	 */
	public String getTeamName() {
		return this.teamName;
	}

	/**
	 * Sets the teamName
	 * 
	 * @param teamName
	 */
	public void setTeamName(String teamName) {
		this.teamName = teamName;
	}

	/**
	 * Tests, if team is east side starting
	 * 
	 * @return true, if the team is started on east side
	 */
	public boolean isTeamEast() {
		return this.isTeamEast;
	}

	/**
	 * Sets the side of the team
	 * 
	 * @param is -
	 *            true, if team plays on east side
	 */
	public void setTeamEast(boolean is) {
		this.isTeamEast = is;
	}

	/**
	 * Checks if the mode is online coach
	 */
	public boolean isOnline() {
		return (this.mode == COACH);
	}

	/**
	 * Retrieves the mode of the player (one of TRAINER, COACH, GOALIE or FIELD)
	 * 
	 * @return
	 */
	public int getMode() {
		return this.mode;
	}

	/**
	 * This method makes the player disconnect from the server and stopping the
	 * thread. This will cause the player to be killed.
	 */
	public void disconnect() {
		this.running = false;
		try {
			this.connection.send("(bye)");
			System.out.println(this.connection.receive());
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
